/**
 * 
 */
package com.ofek.eventos.net.lks;

import java.util.ArrayList;


/**
 * @author Ofek
 *
 */
public class LastKnownSyncer<IDT,IT> {

	protected final IDistributeableDatabaseHandle<IDT,IT> databaseInterface;
	private final String name;
	protected final ISyncer<IDT,IT> syncerOut;

	public LastKnownSyncer(final String name,final IDistributeableDatabaseHandle<IDT,IT> databaseHandle,final ISyncer<IDT,IT> syncerOut) {
		databaseInterface = databaseHandle;
		this.name = name;
		this.syncerOut = syncerOut;
	}


	/**
	 * To be used on the client side
	 * @param syncIn
	 * @return
	 */
	public void sync() throws Exception {
		sync((ArrayList<ItemVersion<IDT,IT>>)null);
	}

	/**
	 * To be used on the client side, only one thread can sync at a time (enforced)
	 * @param syncIn
	 * @return
	 */
	public synchronized void sync(final ArrayList<ItemVersion<IDT,IT>> newItems) throws Exception {
		try {
			databaseInterface.lock();
			final LastKnownHandler<IDT> lastKnowns = databaseInterface.getLastKnowns();
			Sync<IDT,IT> syncIn = syncerOut.syncOut(new Sync<IDT,IT>(name,lastKnowns,newItems));
			ArrayList<ItemVersion<IDT, IT>> pendings = databaseInterface.getPendings(syncIn);
			while ( !syncIn.lastKnowns.equals(lastKnowns) || !pendings.isEmpty()) {
				final Sync<IDT,IT> syncOut = privateSyncIteration(syncIn);
				syncIn = syncerOut.syncOut(syncOut);
				pendings = databaseInterface.getPendings(syncIn);
			}
		} finally {
			databaseInterface.unlock();
		}

	}


	/**
	 * To be used on the server side
	 * @param syncIn
	 * @return
	 */
	public Sync<IDT,IT> syncIteration(final Sync<IDT,IT> syncIn) {
		try {
			databaseInterface.lock();
			return privateSyncIteration(syncIn);
		} finally {
			databaseInterface.unlock();
		}

	}
	/**
	 * To be used on the server side
	 * @param syncIn
	 * @return
	 */
	private Sync<IDT,IT> privateSyncIteration(final Sync<IDT,IT> syncIn) {

		preSyncIn(syncIn);
		final Sync<IDT,IT> syncOut;
		final ArrayList<ItemVersion<IDT,IT>> itemsToSend = databaseInterface.getPendings(syncIn);
		final LastKnownHandler<IDT> lastKnown = databaseInterface.syncItems(syncIn);
		syncOut = new Sync<IDT,IT>(name, (LastKnownHandler<IDT>) lastKnown.clone(),itemsToSend);
		postSyncIn(syncOut);
		return syncOut;


	}

	/** after synced in, syncOut is what this node is about to send to the other node
	 * @param syncOut
	 */
	protected void postSyncIn(final Sync<IDT,IT> syncOut) {
	}
	/** before sync in
	 * @param syncIn
	 */
	protected void preSyncIn(final Sync<IDT,IT> syncIn) {
	}
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return databaseInterface.toString();
	}


	/**
	 * 
	 */
	public void close() {
		if ( syncerOut!=null ) syncerOut.close();
		databaseInterface.close();
	}

}
